/*
 * Copyright (C) 2018-2020 Alibaba Group Holding Limited
 */


#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_MESH_DEBUG_OTA)
#include "common/log.h"
#include "ble_dfu_port.h"
#include "ble_ota.h"
#include "yoc/ble_ais.h"

static ble_ais_ctx_t ble_ais_ctx;
extern ble_ota_ctx_t ble_ota_ctx;

static struct bt_gatt_attr _ais_srv_attrs[];
static struct bt_gatt_indicate_params *p_indicate = NULL;

#define TAG "BLE_AIS"

static void _ais_decrypt(uint8_t *payload, uint8_t len)
{
#if 0
    uint8_t dec[16];

    genie_crypto_decrypt(payload, dec);
    memcpy(payload, dec, 16);
    LOGD(TAG,"dec: %s", bt_hex(payload, 16));
#endif
}

static void _ais_indicate_rsp(struct bt_conn *conn, const struct bt_gatt_attr *attr, u8_t err)
{
    //TODO
    LOGD(TAG,"err 0x%02x", err);
    if (p_indicate) {
        // aos_free(p_indicate);
        //  p_indicate = NULL;
    }
}

struct bt_gatt_indicate_params g_indicate;


static void _ais_server_indicate(uint8_t msg_id, uint8_t cmd, uint8_t *p_msg, uint16_t len)
{
    ais_pdu_t msg;

    LOGD(TAG,"msg_id %02x %02x", msg_id, cmd);
    if (!p_indicate) {
        p_indicate = &g_indicate;
    }

    if (!p_indicate) {
        LOGE(TAG,"no mem");
        return;
    }

    memset(&msg, 0, sizeof(msg));
    if (ble_ais_ctx.state == AIS_STATE_IDLE) {
        msg.header.enc = 1;
    } else {
        msg.header.enc = 0;
    }
    msg.header.msg_id = msg_id;
    msg.header.cmd = cmd;
    msg.header.payload_len = len;
    if (p_msg) {
        memcpy(msg.payload, p_msg, len);
    }

    LOGD(TAG,"len %d: %s", len + 4, bt_hex(&msg, len + 4));

    //indicate._req
    p_indicate->attr = &_ais_srv_attrs[6];
    p_indicate->func = _ais_indicate_rsp;
    p_indicate->data = &msg;
    p_indicate->len = len + 4;
    if (ble_ais_ctx.p_conn) {
        bt_gatt_indicate(ble_ais_ctx.p_conn, p_indicate);
    }
}

void ble_ais_notify(uint8_t msg_id, uint8_t cmd, uint8_t *p_msg, uint16_t len)
{
    ais_pdu_t msg;

    LOGD(TAG,"msg_id %02x %02x", msg_id, cmd);

    memset(&msg, 0, sizeof(msg));
    if (ble_ais_ctx.state >= AIS_STATE_IDLE && ble_ais_ctx.state <= AIS_STATE_REBOOT) {
        msg.header.enc = 1;
    } else {
        msg.header.enc = 0;
    }

    msg.header.cmd = cmd;
    msg.header.msg_id = msg_id;
    msg.header.payload_len = len;
    if (p_msg) {
        memcpy(msg.payload, p_msg, len);
    }

    LOGD(TAG,"len %d: %s", len + 4, bt_hex(&msg, len + 4));

    if (ble_ais_ctx.p_conn) {
        bt_gatt_notify(ble_ais_ctx.p_conn, &_ais_srv_attrs[11], &msg, len + 4);
    }
}

static void handle_ais_disconnect(void)
{
    if (ble_ais_ctx.p_conn) {
        bt_conn_disconnect(ble_ais_ctx.p_conn, BT_HCI_ERR_LOCALHOST_TERM_CONN);
        ble_ais_ctx.p_conn = NULL;
    }
}

void ble_ais_disconnect(uint8_t reason)
{
    if(!ble_ais_ctx.init_flag) {
	   return;
	}
    LOGI(TAG,"dis 0x%x, state %d", reason, ble_ais_ctx.state);
    if (ble_ais_ctx.state != AIS_STATE_REBOOT) {
        k_timer_stop(&ble_ais_ctx.state_update_timer);
        handle_ais_disconnect();

        ble_ais_ctx.state = AIS_STATE_DISCON;
        /* Flash is dirty, need erase */
        if (ble_ota_ctx.flash_clean == 1 &&
            ble_ota_ctx.ota_ready == 0) {
            erase_dfu_flash();
            ble_ota_ctx.flash_clean = 0;
        }
        /* restart adv */
        //genie_event(GENIE_EVT_SDK_AIS_DISCON, NULL);
    } else {
        if (ble_ota_ctx.ota_flag != OTA_FLAG_SILENT || sal_ota_is_allow_reboot()) {
            //clear image change
            LOGI(TAG,"OTA Reboot!");
            //TODO
            //genie_storage_delete_reliable(GFI_OTA_IMAGE_ID);
            dfu_reboot();
        }
    }
}

static void ble_ais_state_update(void)
{
    LOGD(TAG," %d", ble_ais_ctx.state);

    if (ble_ais_ctx.p_conn == NULL) {
        LOGW(TAG,"proxy is disconnected");
        return;
    }

    switch (ble_ais_ctx.state) {
    case AIS_STATE_DISCON:
        k_timer_stop(&ble_ais_ctx.state_update_timer);
        handle_ais_disconnect();
        break;
    case AIS_STATE_CONNECT:
    case AIS_STATE_IDLE:
        //no disconnect after one minute
        //k_timer_start(&ble_ais_ctx.state_update_timer, AIS_OTA_DISCONN_TIMEOUT);
        break;
    case AIS_STATE_AUTH:
        k_timer_start(&ble_ais_ctx.state_update_timer, AIS_OTA_AUTH_TIMEOUT);
        break;
    case AIS_STATE_OTA:
        k_timer_start(&ble_ais_ctx.state_update_timer, AIS_OTA_REPORT_TIMEOUT);
        break;
    case AIS_STATE_REBOOT:
        k_timer_start(&ble_ais_ctx.state_update_timer, AIS_OTA_REBOOT_TIMEOUT);
        k_timer_start(&ble_ais_ctx.disconnect_timer, AIS_DISCONNECT_TIMEOUT);
        break;
    default:
        break;
    }
}

static void state_update_timer_cb(void *p_timer, void *args)
{
    LOGI(TAG,"timer cb %d", ble_ais_ctx.state);
    switch (ble_ais_ctx.state) {
    case AIS_STATE_CONNECT:
    case AIS_STATE_IDLE:
        ble_ais_ctx.state = AIS_STATE_DISCON;
        break;
    case AIS_STATE_AUTH:
        //ble_ota_reset();
        ble_ais_ctx.state = AIS_STATE_CONNECT;
        break;
    case AIS_STATE_OTA:
        if (ble_ota_ctx.err_count++ >= OTA_RECV_MAX_ERR_COUNT) {
            LOGE(TAG,"OTA failed");
            ble_ais_ctx.state = AIS_STATE_IDLE;
            ble_ota_ctx.rx_len = 0;
            _ais_server_indicate(0, AIS_RESP_ERR, NULL, 0);
        } else {
            ble_ota_status_report();
        }
        break;
    case AIS_STATE_REBOOT:
		dfu_reboot();
        break;
    default:
        break;
    }
    ble_ais_state_update();
}


void ble_ais_connect(struct bt_conn *p_conn)
{
    int ret = 0;
    if(!ble_ais_ctx.init_flag) {
	   ret = bt_conn_disconnect(p_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
	   if(ret) {
          LOGD(TAG,"Disconnect faild %d",ret);
	   }
	   return ;
	}
    if (ble_ais_ctx.state == AIS_STATE_REBOOT) {
       LOGE(TAG,"Ais state reboot,not allowed to connect");
	   bt_conn_disconnect(p_conn, BT_HCI_ERR_LOCALHOST_TERM_CONN);
    }else{
        LOGD(TAG,"status %d", ble_ais_ctx.state);
        ble_ais_ctx.p_conn = p_conn;
        ble_ais_ctx.state = AIS_STATE_CONNECT;
        ble_ais_state_update();
        // genie_event(GENIE_EVT_SDK_AIS_CONNECT, NULL);
	}
}

static void _ais_dis_timer_cb(void *p_timer, void *args)
{
    handle_ais_disconnect();
}

static bool _ais_scrt_random(uint8_t msg_id, ais_scrt_random_t *p_scrt_random)
{
    uint8_t cipher[16];

    ble_ais_ctx.state = AIS_STATE_AUTH;
    //ble_ais_get_cipher(p_scrt_random->random, cipher);
    _ais_server_indicate(msg_id, AIS_SCRT_CIPHER, cipher, 16);

    return true;
}

static bool _ais_scrt_result(uint8_t msg_id, ais_scrt_result_t *p_scrt_result)
{
    uint8_t ack = 0;

    if (p_scrt_result->result == 1) {
        //TODO
        //ble_ais_reset();
        ble_ais_ctx.state = AIS_STATE_CONNECT;
    } else {
        ble_ais_ctx.state = AIS_STATE_IDLE;
    }

    _ais_server_indicate(msg_id, AIS_SCRT_ACK, &ack, 1);

    return true;
}

static bool _ais_link_ack(uint8_t msg_id, ais_scrt_result_t *p_scrt_result)
{
    uint8_t plaine_data[GENIE_CRYPTO_UNIT_SIZE];
    //uint8_t encrypt_data[GENIE_CRYPTO_UNIT_SIZE];

    if (p_scrt_result->result == 0) {
        // ble_ota_servicereset();
        ble_ais_ctx.state = AIS_STATE_CONNECT;
    }
    memset(plaine_data, 0x0F, sizeof(plaine_data));
    plaine_data[0] = 1;

    //genie_crypto_encrypt(plaine_data, encrypt_data);
    _ais_server_indicate(msg_id, AIS_LINK_ACK, plaine_data, GENIE_CRYPTO_UNIT_SIZE);

    return true;
}

static bool _ais_msg_check_header(ais_header_t *p_msg_header)
{
    //check seq & total, in ota case, the seq & total must be 0
    if (p_msg_header->total_frame != 0 || p_msg_header->seq != 0 || p_msg_header->ver != 0 || p_msg_header->seq > p_msg_header->total_frame) {
        LOGE(TAG,"fail %s", bt_hex(p_msg_header, sizeof(ais_header_t)));
        return false;
    }
    return true;
}

static void _ais_server_msg_handle(struct bt_conn *p_conn, ais_pdu_t *p_msg, uint16_t len)
{
    bool timer_refresh = false;
    LOGD(TAG,"ais state %d cmd %02x",ble_ais_ctx.state, p_msg->header.cmd);

    if(p_msg->header.cmd != AIS_OTA_DATA) {
        LOGD(TAG,"len %d: %s", len, bt_hex(p_msg, len));
    }

    if (p_msg->header.cmd != AIS_OTA_DATA && !_ais_msg_check_header((ais_header_t *)p_msg)) {
        LOGE(TAG,"invalid msg, ignore");
    }

    if (p_msg->header.cmd != AIS_OTA_DATA) {
        LOGI(TAG,"AIS Cmd:0x%02x", p_msg->header.cmd);
    }

    switch (p_msg->header.cmd) {
    case AIS_SCRT_RANDOM:
        //len = 4+16
        if (len == 20 && (ble_ais_ctx.state == AIS_STATE_CONNECT || ble_ais_ctx.state == AIS_STATE_IDLE)) {
            timer_refresh = _ais_scrt_random(p_msg->header.msg_id,
                                             (ais_scrt_random_t *)p_msg->payload);
        }
        break;

    case AIS_SCRT_RESULT:
        //len = 4+1
        if (len == 5 && ble_ais_ctx.state == AIS_STATE_AUTH) {
            timer_refresh = _ais_scrt_result(p_msg->header.msg_id,
                                             (ais_scrt_result_t *)p_msg->payload);
        }
        break;

    case AIS_LINK_STATUS:
        //len = 4+16
        if (len == 20 && ble_ais_ctx.state == AIS_STATE_IDLE) {
            _ais_decrypt(p_msg->payload, 16);
            timer_refresh = _ais_link_ack(p_msg->header.msg_id,
                                          (ais_scrt_result_t *)p_msg->payload);
        }
        break;

    case AIS_OTA_VER_REQ:
        if ((len == 20 && ble_ais_ctx.state == AIS_STATE_IDLE) || (len == 5 && ble_ais_ctx.state == AIS_STATE_CONNECT)) {
            if (ble_ais_ctx.state == AIS_STATE_IDLE) {
                _ais_decrypt(p_msg->payload, 16);
            }
            timer_refresh = ble_ota_handle_version_request(p_msg->header.msg_id,
                            (ais_ota_ver_req_t *)p_msg->payload);
            //TODO
            ble_ais_ctx.state = AIS_STATE_IDLE;
        }
        break;

    case AIS_OTA_FIRMWARE_REQ:
        //len = 4+16
        //TODO
        if (len == 16 && ble_ais_ctx.state == AIS_STATE_IDLE) {
            _ais_decrypt(p_msg->payload, 16);
            timer_refresh = ble_ota_handle_update_request(p_msg->header.msg_id,
                            (ais_ota_upd_req_t *)p_msg->payload);
        }
        break;

    case AIS_OTA_DATA:
        if (len == sizeof(ais_header_t) + p_msg->header.payload_len && p_msg->header.ver == 0 && ble_ais_ctx.state == AIS_STATE_OTA) {
            timer_refresh = ble_ota_parse_pdu(p_msg);
        }
        break;

    case AIS_OTA_CHECK_REQ:
        //TODO len 20->5
        if (len == 5 && ble_ais_ctx.state == AIS_STATE_OTA) {
            _ais_decrypt(p_msg->payload, 16);
            timer_refresh = ble_ota_check_firmware(p_msg->header.msg_id, (ais_ota_check_req_t *)p_msg->payload);
        }
        break;

    default:
        /* recv some unsupport cmd, just return */
        LOGW(TAG,"unsupport cmd %x", p_msg->header.cmd);
        return;
    }

    if (timer_refresh) {
        ble_ais_state_update();
    } else {
        if (ble_ais_ctx.state != AIS_STATE_OTA) {
            _ais_server_indicate(p_msg->header.msg_id, AIS_RESP_ERR, NULL, 0);
        }
    }
}

static ssize_t _ais_server_read(struct bt_conn *p_conn, const struct bt_gatt_attr *p_attr,
                                void *buf, u16_t len, u16_t offset)
{
    u16_t *value = p_attr->user_data;

    LOGD(TAG,"len %d: %s", len, bt_hex(buf, len));

    return bt_gatt_attr_read(p_conn, p_attr, buf, len, offset, value, sizeof(*value));
}

static ssize_t _ais_service_write(struct bt_conn *p_conn, const struct bt_gatt_attr *p_attr,
                                  const void *p_buf, u16_t len, u16_t offset, u8_t flags)
{
    //LOGD(TAG,"len %d: %s", len, bt_hex(p_buf, len));

    if (len != 0) {
        _ais_server_msg_handle(p_conn, (ais_pdu_t *)p_buf, len);
    }

    return len;
}

static ssize_t _ais_service_write_nr(struct bt_conn *p_conn, const struct bt_gatt_attr *p_attr,
                                     const void *p_buf, u16_t len, u16_t offset, u8_t flags)
{
    //LOGD(TAG,"len %d: %s", len, bt_hex(p_buf, len));
    return _ais_service_write(p_conn, p_attr, p_buf, len, offset, flags);
}

static void _ais_service_ccc_cfg_changed(const struct bt_gatt_attr *p_attr, uint16_t value)
{
}

/* AIS OTA Service Declaration */
static struct bt_gatt_attr _ais_srv_attrs[] = {
    BT_GATT_PRIMARY_SERVICE(AIS_SERVICE_UUID),

    BT_GATT_CHARACTERISTIC(AIS_READ_UUID, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, _ais_server_read, NULL, NULL),

    BT_GATT_CHARACTERISTIC(AIS_WRITE_UUID, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, BT_GATT_PERM_WRITE,
                           NULL, _ais_service_write, NULL),

    BT_GATT_CHARACTERISTIC(AIS_INDICATE_UUID, BT_GATT_CHRC_READ | BT_GATT_CHRC_INDICATE,
                           BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, _ais_server_read, NULL, NULL),

    BT_GATT_CCC(_ais_service_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),

    BT_GATT_CHARACTERISTIC(AIS_WRITE_WO_RESP_UUID, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE_WITHOUT_RESP,
                           BT_GATT_PERM_WRITE, NULL, _ais_service_write_nr, NULL),

    BT_GATT_CHARACTERISTIC(AIS_NOTIFY_UUID, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, _ais_server_read, NULL, NULL),

    BT_GATT_CCC(_ais_service_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
};

static struct bt_gatt_service _ais_srv = BT_GATT_SERVICE(_ais_srv_attrs);



int ble_ais_state_set(uint8_t state)
{
    ble_ais_ctx.state = state;

    return 0;
}

uint8_t ble_ais_state_get(void)
{
    return ble_ais_ctx.state;
}


int ble_ais_server_init(void)
{
    int ret =0;
    ret = bt_gatt_service_register(&_ais_srv);
    if(ret) {
        LOGE(TAG,"gatt register service faild");
        return ret;
    }
    memset(&ble_ais_ctx, 0, sizeof(ble_ais_ctx));

    k_timer_init(&ble_ais_ctx.state_update_timer, state_update_timer_cb, NULL);
    k_timer_init(&ble_ais_ctx.disconnect_timer, _ais_dis_timer_cb, NULL);
    ble_ais_ctx.init_flag = 1;
    return 0;
}

